// CS 564 DATABASES : PROJECT STAGE 3
// Caela Northey (caela) 905 653 2238
// Colin Laska   (laska)
//
// DUE 10/18/12 6pm
//
// Stage 3 of 7 for the class project, first of the C++ stages. This
// is the implementation of a buffer manager for our database system,
// (Minirel). Two other classes are involved in the Minirel buffer
// manager:
//    BufDesc    array of BufDesc instances describes the state of
//               each corresponding frame in the buffer pool
//    BufHashTbl single instance tracks which pages are currently in
//               the buffer pool
// 
// We were responsible for writing 4 methods of this class: allocBuf(),
// readPage(), unPinPage(), and allocPage().

#include <memory.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
#include <fcntl.h>
#include <iostream>
#include <stdio.h>
#include "page.h"
#include "buf.h"

#define ASSERT(c)  { if (!(c)) { \
		       cerr << "At line " << __LINE__ << ":" << endl << "  "; \
                       cerr << "This condition should hold: " #c << endl; \
                       exit(1); \
		     } \
                   }

//----------------------------------------
// Constructor of the class BufMgr
//----------------------------------------

BufMgr::BufMgr(const int bufs)
{
    numBufs = bufs;

    bufTable = new BufDesc[bufs];
    memset(bufTable, 0, bufs * sizeof(BufDesc));
    for (int i = 0; i < bufs; i++) 
    {
        bufTable[i].frameNo = i;
        bufTable[i].valid = false;
    }

    bufPool = new Page[bufs];
    memset(bufPool, 0, bufs * sizeof(Page));

    int htsize = ((((int) (bufs * 1.2))*2)/2)+1;
    hashTable = new BufHashTbl (htsize);  // allocate the buffer hash table

    clockHand = bufs - 1;

}


BufMgr::~BufMgr() {

    // flush out all unwritten pages
    for (int i = 0; i < numBufs; i++) 
    {
        BufDesc* tmpbuf = &bufTable[i];
        if (tmpbuf->valid == true && tmpbuf->dirty == true) {

#ifdef DEBUGBUF
            cout << "flushing page " << tmpbuf->pageNo
                 << " from frame " << i << endl;
#endif

            tmpbuf->file->writePage(tmpbuf->pageNo, &(bufPool[i]));
        }
    }

    delete [] bufTable;
    delete [] bufPool;
}

// Allocates a free frame using the Clock Algorithm; if 
// necessary, writing a dirty page back to disk. Called by
// readPage() and allocPage() below.
//
// RETURNS: OK
//          BUFFEREXCEEDED  if all frames pinned
//          UNIXERR         I/O call returned error
//
// NOTE: make sure to remove hash table entry if buffer frame
//       allocated has a valid page in it
// NOTE: if clock hand goes completely around twice, know that
//       buffer is full b/c first round would clears all refbits,
//       then would search for available pages (ie, invalid or 
//       unpinned). thus, 2x => no such pages.
const Status BufMgr::allocBuf(int & frame) 
{
    // do while frames available

    Status s;
		int stop = 2*numBufs;
    int count = 0;

    do {
      cout << "CLK @: " << clockHand << " | num_pinned: " << num_pinned << "| numBufs: " << numBufs << "\n";

        // increment clock hand
        advanceClock();
        count++;
        if (!bufTable[clockHand].valid){     // valid set?
            bufTable[clockHand].Clear();
            frame = clockHand;
            return OK;
        }

        if (bufTable[clockHand].refbit) {    // refBit set?
            bufTable[clockHand].refbit = false;
            continue;
        }

        if (bufTable[clockHand].pinCnt > 0) { //page pinned?

            num_pinned++;

            continue;
        }

				// by here, have found a usable frame

        // so, (1) check if dirty bit set:
        if (bufTable[clockHand].dirty) {

            //writePage() returns OK, BADPAGENO, BADPAGEPTR, or UNIXERR.
            s = (bufTable[clockHand].file)->writePage(bufTable[clockHand].pageNo, &bufPool[clockHand]);

            if (s != OK) {
                return s;
            }
        }


        // and then (2) bookkeeping:
        hashTable->remove(bufTable[clockHand].file, bufTable[clockHand].pageNo); // (fail silently if remove errors)

        bufTable[clockHand].Clear();

				// and (3) return:
        frame = clockHand;
        return OK;

    } while(count < stop); // do while end

    return BUFFEREXCEEDED;
}

// First, checks whether the page is already in the buffer pool (by
// using lookup() on the hashtable to get a frame number). There are
// two cases to be handled depending on the outcome of the lookup() call
//   1) page NOT in buffer pool: call allocBuf() for new frame, then
//      file->readPage() to get page from disk into frame, then insert
//      page into hashtable, then set() frame. return a ptr to frame
//      containing page via page parameter.
//   2) page in buffer pool: set the appropriate refbit, pinCnt++,
//      then return a ptr to the frame containing page via page param
//
// RETURNS: OK
//          HASHTBLERROR   if hash table error
//          BUFFEREXCEEDED if all fames are pinned
//          UNIXERR        if unix error
const Status BufMgr::readPage(File* file, const int PageNo, Page*& page)
{
    Status s;
    int frame_num;

    s = hashTable->lookup(file, PageNo, frame_num); // Get frame_num if (file, pageNo) in hash table

    if (s == OK) {
        bufTable[frame_num].refbit = true;
        bufTable[frame_num].pinCnt++;
        page = &bufPool[frame_num];
        return OK;
    }

    else {

        s = allocBuf(frame_num); // Get a frame by kicking out another one

        if (s != OK){
            return s;
        }

        s = file->readPage(PageNo, &bufPool[frame_num]); // Read from disk into frame

        if (s != OK) {
            return s;
        }

        s = hashTable->insert(file, PageNo, frame_num); // Insert relation into hashtable

        if (s != OK) {
            return s;
        }

        bufTable[frame_num].Set(file, PageNo); // Update bufDesc for this frame
        page = &bufPool[frame_num];
        return OK;
		}
}

// Decrements the pinCnt of the frame containing (file, PageNo)
// and, if dirty == true, sets the dirty bit.
//
// RETURNS: OK
//          PAGENOTPINNED if pin count already = 0
//          HASHNOTFOUND if page not in buffer pool
const Status BufMgr::unPinPage(File* file, const int PageNo, 
			       const bool dirty) 
{
    Status s;
    int frame_num;

		//get frame from lookup
    s = hashTable->lookup(file, PageNo, frame_num);

		// error if not found or not pinned
    if (s != OK) {
        return s;
    }

    if (bufTable[frame_num].pinCnt <= 0) {
        return PAGENOTPINNED;
    }

    // decrement pinCnt and set dirty bit
    else {
				bufTable[frame_num].pinCnt--;
        if (dirty) {
            bufTable[frame_num].dirty = true;
        }
    }

    return OK;
}

// First, allocates an empty page in the specified file by invoking
// file->allocatePage(), which returns the page number of the newly
// allocated page.  Then, allocBuf() is called to obtain a buffer pool
// frame.  Next, an entry is inserted into the hash table and Set()
// is invoked on the frame to set it up properly.
//
// RETURNS: page number of new page via pageNo param
//          ptr to buffer frame via page param
//          OK
//          BUFFEREXCEEDED
//          HASHTBLERROR
//          UNIX
const Status BufMgr::allocPage(File* file, int& pageNo, Page*& page) 
{
    Status s;
    int frame_num;

    s = file->allocatePage(pageNo); // Allocate page on disk; fills pageNo with proper pageNo

    if (s != OK) {
        return s;
    }

    s = allocBuf(frame_num); // Get a frame by kicking out another one

    if (s != OK) {
        return s;
    }

    s = hashTable->insert(file, pageNo, frame_num); // Insert relation into hashtable

    if (s != OK) {
        return s;
    }

    bufTable[frame_num].Set(file, pageNo); // Update bufDesc for this frame
    page = &bufPool[frame_num];
    return OK;
}

// no desc given in write up
const Status BufMgr::disposePage(File* file, const int pageNo) 
{
    // see if it is in the buffer pool
    Status status = OK;
    int frameNo = 0;
    status = hashTable->lookup(file, pageNo, frameNo);
    if (status == OK)
    {
        // clear the page
        bufTable[frameNo].Clear();
    }
    status = hashTable->remove(file, pageNo);

    // deallocate it in the file
    return file->disposePage(pageNo);
}

// Called by DB::closeFile() when all instances of a file have been
// closed (in which case all pages of the file should have been
// unpinned). flushFile() should scan bufTable for pages belonging
// to the file. FOr each page:
//    a) if the page is dirty, call file->writePage() to flush
//       page to disk, then set the dirty bit to false
//    b) remove the page from the hashtable (whether the page 
//       is clean or dirty)
//    c) invoke the Clear() method on the page frame.
//
// RETURNS: OK
//          PAGEPINNED if a page of a file is still pinned
const Status BufMgr::flushFile(const File* file) 
{
  Status status;

  for (int i = 0; i < numBufs; i++) {
    BufDesc* tmpbuf = &(bufTable[i]);
    if (tmpbuf->valid == true && tmpbuf->file == file) {

      if (tmpbuf->pinCnt > 0)
	  return PAGEPINNED;

      if (tmpbuf->dirty == true) {
#ifdef DEBUGBUF
	cout << "flushing page " << tmpbuf->pageNo
             << " from frame " << i << endl;
#endif
	if ((status = tmpbuf->file->writePage(tmpbuf->pageNo,
					      &(bufPool[i]))) != OK)
	  return status;

	tmpbuf->dirty = false;
      }

      hashTable->remove(file,tmpbuf->pageNo);

      tmpbuf->file = NULL;
      tmpbuf->pageNo = -1;
      tmpbuf->valid = false;
    }

    else if (tmpbuf->valid == false && tmpbuf->file == file)
      return BADBUFFER;
  }
  
  return OK;
}


void BufMgr::printSelf(void) 
{
    BufDesc* tmpbuf;
  
    cout << endl << "Print buffer...\n";
    for (int i=0; i<numBufs; i++) {
        tmpbuf = &(bufTable[i]);
        cout << i << "\t" << (char*)(&bufPool[i]) 
             << "\tpinCnt: " << tmpbuf->pinCnt;
    
        if (tmpbuf->valid == true)
            cout << "\tvalid\n";
        cout << endl;
    };
}


